package org.rcgwt.client.ui.calendar.renderer.impl;

import java.util.Date;

import org.rcgwt.client.ui.calendar.controller.CalendarAction;
import org.rcgwt.client.ui.calendar.controller.CalendarChangesMask;
import org.rcgwt.client.ui.calendar.controller.CalendarController;
import org.rcgwt.client.ui.calendar.event.ModelChangedEvent;
import org.rcgwt.client.ui.calendar.event.ModelChangedHandler;
import org.rcgwt.client.ui.calendar.i18n.CalendarUtils;
import org.rcgwt.client.ui.calendar.model.impl.CalendarModel;
import org.rcgwt.client.ui.calendar.model.impl.SelectionType;
import org.rcgwt.client.ui.calendar.mvc.ICalendarContext;
import org.rcgwt.client.ui.calendar.renderer.IHeaderRenderer;
import org.rcgwt.client.ui.themes.ITheme;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.Cell;

/**
 * A full calendar month and year cursor
 * 
 * @author Elian ORIOU
 * 
 */

@SuppressWarnings("deprecation")
public class FullCalendarHeaderRenderer implements IHeaderRenderer {

	/* Constants */
	private static final int MONTH_DISPLAYED_LENGTH = 3;

	private ITheme theme;
	private CalendarController controller;
	private CalendarModel model;

	/* Panels */
	private FlexTable yearPanel;

	/* Controls */
	private Label l_year;
	private Label b_previousYear;
	private Label b_nextYear;
	private Grid monthGrid;

	private int row;
	private int cell;

	/* Constants */
	private static String[] monthes = CalendarUtils.getShortMonths();

	public FullCalendarHeaderRenderer() {
	}

	/**
	 * Rendering method
	 */

	@Override
	public Widget render(ICalendarContext context) {

		theme = context.getTheme();
		controller = context.getController();
		model = context.getModel();

		/* Controls Management */
		VerticalPanel vp = new VerticalPanel();

		/* Year Controls */

		yearPanel = new FlexTable();
		yearPanel.setStyleName(theme.getFullYearPanelStyle().getClassName());

		l_year = new Label(String.valueOf(model.getCurrentYear()));
		l_year.setStyleName(theme.getYearLabelStyle().getClassName());

		b_previousYear = new Label();
		b_previousYear.setStyleName(theme.getPreviousYearButtonStyle()
				.getClassName());
		b_nextYear = new Label();
		b_nextYear.setStyleName(theme.getNextYearButtonStyle().getClassName());

		yearPanel.setWidget(0, 0, b_previousYear);
		yearPanel.setWidget(0, 1, l_year);
		yearPanel.setWidget(0, 2, b_nextYear);

		/* Month Controls */

		monthGrid = new Grid(2, 6);
		monthGrid.setStyleName(theme.getFullMonthPanelStyle().getClassName());

		int row = 0;
		int col = 0;
		boolean ok = false;

		for (int i = 0; i < monthes.length; i++) {
			if (i >= 6 && ok != true) {
				row = 1;
				col = 0;
				ok = true;
			}
			Label l = new Label(monthes[i]);
			if (MONTH_DISPLAYED_LENGTH > 0) {
				l = new Label(monthes[i].substring(0, MONTH_DISPLAYED_LENGTH));
			}
			l.addStyleName(theme.getMonthesStyle().getClassName());
			monthGrid.setWidget(row, col, l);
			col++;
		}

		vp.add(yearPanel);
		vp.add(monthGrid);

		createHandlers();

		return vp;
	}

	/**
	 * Creates and manages handlers
	 */

	private void createHandlers() {
		l_year.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				controller.handle(CalendarAction.SELECT_YEAR_CURSOR);
				controller.setSelectionType(SelectionType.YEAR);
			}
		});
		b_previousYear.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				controller.handle(CalendarAction.PREVIOUS_YEAR);
			}
		});
		b_nextYear.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				controller.handle(CalendarAction.NEXT_YEAR);
			}
		});
		monthGrid.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Cell c = monthGrid.getCellForEvent(event);
				if (c == null) {
					return;
				}
				controller.setSelectionType(SelectionType.MONTH);
				deselectMonthes();
				row = c.getRowIndex();
				cell = c.getCellIndex();
				if (controller.setDisplayedMonth(getMonthNumber(row, cell)) == true) {
					selectCell(row, cell);
					controller.handle(CalendarAction.REFRESH_GRID);
				}
			}
		});
		controller.addModelChangedHandler(new ModelChangedHandler() {
			@Override
			public void onModelChangedEvent(ModelChangedEvent event) {
				Date d = event.getDate();
				SelectionType selectionType = model.getSelectionType();

				b_previousYear.setStyleName(theme.getPreviousYearButtonStyle()
						.getClassName());
				b_nextYear.setStyleName(theme.getNextYearButtonStyle()
						.getClassName());

				/* Selection / Deselection (It doesn't require date) */
				if ((event.getElementType() & CalendarChangesMask.SELECT_MONTH) > 0) {
					if (isSelected(l_year) == true) {
						deselectCell(l_year);
					}
					selectDisplayedMonth();
				}
				if ((event.getElementType() & CalendarChangesMask.UNSELECT_MONTH) > 0) {
					deselectMonthes();
				}
				if ((event.getElementType() & CalendarChangesMask.SELECT_YEAR) > 0) {
					deselectMonthes();
					if (isSelected(getMonthLabel(model.getDisplayedMonth())) == true) {
						deselectDisplayedMonth();
					}
					selectYear();
				}
				if ((event.getElementType() & CalendarChangesMask.UNSELECT_YEAR) > 0) {
					deselectYear();
				}
				/* Previous and Next context actions */
				if ((event.getElementType() & CalendarChangesMask.PREVIOUS_CONTEXT) > 0) {
					if (selectionType == SelectionType.MONTH) {
						deselectMonthes();
						selectCell(getMonthLabel(model.getDisplayedMonth()));
					} else if (selectionType == SelectionType.YEAR) {
						l_year
								.setText(String.valueOf(model
										.getDisplayedYear()));
					}
				}
				if ((event.getElementType() & CalendarChangesMask.NEXT_CONTEXT) > 0) {
					if (selectionType == SelectionType.MONTH) {
						deselectMonthes();
						selectCell(getMonthLabel(model.getDisplayedMonth()));
					} else if (selectionType == SelectionType.YEAR) {
						l_year
								.setText(String.valueOf(model
										.getDisplayedYear()));
					}
				}
				if ((event.getElementType() & CalendarChangesMask.MIN_OVERFLOW) > 0) {
					b_previousYear.setStyleName(theme
							.getDisabledPreviousButtonStyle().getClassName());
				}
				if ((event.getElementType() & CalendarChangesMask.MAX_OVERFLOW) > 0) {
					b_nextYear.setStyleName(theme.getDisabledNextButtonStyle()
							.getClassName());
				}

				if (d == null) {
					return;
				}
				int year = d.getYear() + 1900;
				/* HEADER ACTIONS */
				if ((event.getElementType() & CalendarChangesMask.SPECIFIED_YEAR) > 0) {
					l_year.setText(String.valueOf(year));
				}
				if ((event.getElementType() & CalendarChangesMask.SPECIFIED_MONTH) > 0) {
					deselectMonthes();
					selectCell(getMonthLabel(d.getMonth()));
				}
				if ((event.getElementType() & (CalendarChangesMask.SPECIFIED_YEAR | CalendarChangesMask.SPECIFIED_MONTH)) > 0) {
					selectCell(getMonthLabel(d.getMonth()));
					l_year.setText(String.valueOf(year));
				}
				/* FOOTER ACTION */
				if ((event.getElementType() & CalendarChangesMask.CURRENT_DAY) > 0) {
					selectCell(getMonthLabel(d.getMonth()));
					l_year.setText(String.valueOf(year));
				}
			}
		});
	}

	/**
	 * Returns the month number linked to a label in the grid
	 * 
	 * @param row
	 * @param cell
	 * @return
	 */

	private int getMonthNumber(int row, int cell) {
		if (row == 0) {
			return cell;
		} else {
			return cell + 6;
		}
	}

	/**
	 * Returns the label linked to a month number
	 * 
	 * @param month
	 * @return
	 */

	private Label getMonthLabel(int month) {
		if (month < 6) {
			return (Label) monthGrid.getWidget(0, month);
		} else if (month < 12) {
			return (Label) monthGrid.getWidget(1, month - 6);
		}
		return null;
	}

	/**
	 * Deselects month label in the grid
	 */

	private void deselectMonthes() {
		for (int i = 0; i < monthGrid.getRowCount(); i++) {
			for (int j = 0; j < monthGrid.getCellCount(j); j++) {
				Label l = (Label) monthGrid.getWidget(i, j);
				deselectCell(l);
			}
		}
	}

	/**
	 * Deselects the year label
	 */

	private void deselectYear() {
		l_year.removeStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	/**
	 * Selects the year label
	 */

	private void selectYear() {
		l_year.addStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	/**
	 * Selects the displayed month label in the grid
	 */

	private void selectDisplayedMonth() {
		selectCell(getMonthLabel(model.getDisplayedMonth()));
	}

	/**
	 * Deselects the displayed month label in the grid
	 */

	private void deselectDisplayedMonth() {
		deselectCell(getMonthLabel(model.getDisplayedMonth()));
	}

	/**
	 * Selects a cell in a grid (by its position in the grid)
	 * 
	 * @param row
	 * @param cell
	 */

	private void selectCell(int row, int cell) {
		Label l = (Label) monthGrid.getWidget(row, cell);
		selectCell(l);
	}

	/**
	 * Selects a label
	 * 
	 * @param w
	 */

	private void selectCell(Widget w) {
		w.addStyleName(theme.getSelectedMonthesLabelStyle().getClassName());
	}

	/**
	 * Deselects a label
	 * 
	 * @param w
	 */

	private void deselectCell(Widget w) {
		w.removeStyleName(theme.getSelectedMonthesLabelStyle().getClassName());
	}

	/**
	 * Returns true if the label is selected (in the context of the component)
	 * 
	 * @param w
	 * @return
	 */

	private boolean isSelected(Widget w) {
		return w.getStyleName().contains(
				theme.getSelectedYearMonthLabelStyle().getClassName());
	}

	@Override
	public void update(ICalendarContext context) {
	}

	@Override
	public void onKeyDown(KeyDownEvent event) {
		SelectionType type = model.getSelectionType();
		if (type != SelectionType.MONTH && type != SelectionType.YEAR) {
			return;
		}
		int keyCode = event.getNativeKeyCode();
		switch (keyCode) {
		case KeyCodes.KEY_LEFT:
			if (type == SelectionType.MONTH) {
				controller.handle(CalendarAction.PREVIOUS_MONTH);
			} else {
				controller.handle(CalendarAction.PREVIOUS_YEAR);
			}
			break;
		case KeyCodes.KEY_RIGHT:
			if (type == SelectionType.MONTH) {
				controller.handle(CalendarAction.NEXT_MONTH);
			} else {
				controller.handle(CalendarAction.NEXT_YEAR);
			}
			break;
		case KeyCodes.KEY_HOME:
			if (type == SelectionType.MONTH) {
				if (model.getDisplayedMonth() == 0) {
					controller.handle(CalendarAction.PREVIOUS_YEAR);
				}
				controller.setDisplayedMonth(0);
				controller.handle(CalendarAction.REFRESH_CALENDAR);
			}
			break;
		case KeyCodes.KEY_END:
			if (type == SelectionType.MONTH) {
				if (model.getDisplayedMonth() == 11) {
					controller.handle(CalendarAction.NEXT_YEAR);
				}
				controller.setDisplayedMonth(11);
				controller.handle(CalendarAction.REFRESH_CALENDAR);
			}
			break;
		}
	}

	@Override
	public void onKeyUp(KeyUpEvent event) {
	}

	@Override
	public void onMouseWheel(MouseWheelEvent event) {
		SelectionType type = model.getSelectionType();
		if (type != SelectionType.MONTH && type != SelectionType.YEAR) {
			return;
		}
		event.preventDefault();
		if (type == SelectionType.MONTH) {
			if (event.isNorth() == true) {
				controller.handle(CalendarAction.PREVIOUS_MONTH);
			} else if (event.isSouth() == true) {
				controller.handle(CalendarAction.NEXT_MONTH);
			}
		} else {
			if (event.isNorth() == true) {
				controller.handle(CalendarAction.PREVIOUS_YEAR);
			} else if (event.isSouth() == true) {
				controller.handle(CalendarAction.NEXT_YEAR);
			}
		}
	}
}
